import cv2
import cvzone
from cvzone.FaceMeshModule import FaceMeshDetector  # 导入脸部关键点检测方法
from cvzone.PlotModule import LivePlot  # 导入实时绘图模块
 
#（1）读取视频文件
# filepath = 'D:/deeplearning/video/eyes1.mp4'
cap = cv2.VideoCapture(0)
 
#（2）配置
# 接收脸部检测方法，设置参数
detector = FaceMeshDetector(maxFaces=1)  # 最多只检测一张脸
 
# 人眼关键点所在的索引
idList = [22, 23, 24, 26, 110, 130, 157, 158, 159, 160, 161, 243]  # 左眼 
 
# 接收实时绘图方法，图宽640高360，y轴刻度范围，间隔默认0.01，上下翻转默认False
plotY = LivePlot(640, 360, [20,40], invert=True)
 
ratioList = []  # 存放实时的人眼开合百分比
 
blinkCounter = 0  # 眨眼计数器默认=0
 
counter = 0  # 代表当前帧没计算过眨眼次数
 
colorblink = (255,0,255)  # 不眨眼时计数器的颜色
 
#（3）图像处理
while True:
 
    # 原视频较短，循环播放
    if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):  # 如果当前帧等于总帧数，即视频播放到了结尾
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)  # 让当前帧为0，重置视频从头开始
 
    # 返回帧图像是否读取成功success，读取的帧图像img
    success, img = cap.read()
 
    #（4）关键点检测，不绘制关键点及连线
    img, faces = detector.findFaceMesh(img, draw=False) # img为绘制关键点后的图像，faces为关键点坐标
 
    # 如果检测到关键点了就接下去处理
    if faces:
        face = faces[0]  # face接收一张脸的所有关键点信息，且faces是三维列表
 
        # 遍历所有的眼部关键点
        for id in idList:
            cv2.circle(img, tuple(face[id]), 4, (0,255,255), cv2.FILLED)  # 以关键点为圆心半径为5画圆
 
        # 由于人脸距离摄像机的距离的远近会影响眼部上下边界之间的距离，因此通过开合比例来判断是否眨眼
        leftUp = tuple(face[159])  # 左眼最上边界的关键点坐标
        leftDown = tuple(face[23])  # 左眼最下边界的关键点坐标
        leftLeft = tuple(face[130])  # 左眼最左边界的关键点坐标
        leftRight = tuple(face[243])  # 左眼最右边界的关键点坐标
 
        # 计算上下边界以及左右边界的距离，函数返回距离length，以及线段两端点和中点坐标，这里用不到
        lengthver, __ = detector.findDistance(leftUp, leftDown)  # 上下边界距离
        lengthhor, __ = detector.findDistance(leftLeft, leftRight)  # 左右边界距离
        
        # 在上下及左右关键点之间各画一条线段，端点坐标是元组
        cv2.line(img, leftUp, leftDown, colorblink, 2)
        cv2.line(img, leftLeft, leftRight, colorblink, 2)
 
        # 计算竖直距离与水平距离的比值
        ratio = 100*lengthver/lengthhor
        ratioList.append(ratio)  # 存放每帧图像的人眼开合比
 
        #（5）平滑实时变化曲线
        # 每10帧图像的人眼开合百分比取平均值，在图像上更新一个值
        if len(ratioList) > 10:
            ratioList.pop(0)  # 删除最前面一个元素
        ratioAvg = sum(ratioList)/len(ratioList)  # 超过10帧后，保证每10个元素取平均
        print(ratioAvg)
            
        #（6）眨眼计数器
        # 当开合比低于26%并且距前一次计算眨眼已超过10帧时，认为是眨眼
        if ratioAvg < 26 and counter == 0:
            blinkCounter += 1  # 眨眼次数加1
            colorblink = (0,255,0)  # 眨眼时改变当前帧的颜色
            counter = 1  # 当前帧计算了一次眨眼
        
        # 保证在一次眨眼期间只在10帧中计算一次，不再多余的计算眨眼次数
        if counter != 0:  # 代表前面某10帧已经计算过一次眨眼
            counter += 1  # 累计不计算眨眼次数的帧数+1
            if counter > 10:  # 如果距前一次计算眨眼次数超过10帧了，那么下一次可以计算眨眼
                counter = 0
                colorblink = (255,0,255)  # 不眨眼时计数器颜色回到原颜色
 
        # 以时间为x轴，百分比为y轴，绘制实时变化曲线
        imgPlot = plotY.update(ratioAvg, colorblink)  # 参数：每个时刻的y值，线条颜色
 
        # 将计数显示在图上
        cvzone.putTextRect(img, f'BlinkCount:{blinkCounter}',  # 显示内容是字符串类型
                           (850,80), 4, 3, colorR=colorblink) # 设置文本显示位置、颜色、线条
 
 
        # 重塑图像的宽和高，保证图像size和曲线图size一致
        img = cv2.resize(img, (640,360))  
 
        # 将变化曲线和原图像组合起来，2代表排成两列，最后一个1代表比例不变
        imgStack = cvzone.stackImages([img, imgPlot], 2, 1)
 
    #（7）显示图像
    cv2.imshow('img', imgStack)  # 传入窗口名和帧图像
 
    # 每帧图像滞留20毫秒后消失，按下键盘上的ESC键退出程序
    if cv2.waitKey(20) & 0xFF == 27:
        break
 
# 释放视频资源
cap.release()
cv2.destroyAllWindows()